#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include <sys/types.h>
//#include <sys/capability.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
//#include <netinet/inet.h>

#include <linux/types.h>

#include <ifaddrs.h>
#include <poll.h>

// ================================================================================

struct ip_header {
  unsigned version : 4;
  unsigned header_len : 4;
  __u8     tos;
  __u16    total_len;
  __u16    id;
  unsigned flags : 3;
  unsigned frag_offset : 13;
  __u8     ttl;
  __u8     proto;
  __u16    checksum;
  __u32    source;
  __u32    dest;
};

#define IP_P_ICMP 0x01

// --------------------------------------------------------------------------------

struct icmp_header {
  //struct ip_header ip;

  __u8 type;
  __u8 code;
  __u16 checksum;
};

struct icmp_amr_frame {
  struct icmp_header icmp;
  
  __u16 id;
  __u16 seqn;
  __u8  network_mask[4];
};

#define ICMP_TYPE_ADDRESS_MASK_REQUEST 17
#define ICMP_TYPE_ADDRESS_MASK_REPLY   18

// ================================================================================

static __u16 ip_checksum(__u16 *data, int len) {
  int i;
  __u16 checksum = 0;

  for (int i = 0; i < len/sizeof(__u16); ++i) {
    checksum += data[i];
  }

  return ~checksum;
}

int main(int argc, char *argv[]) {
  struct icmp_amr_frame iam;
  struct ip_header *ipp;
  struct ifaddrs *ifs, *iface;

  /*
  cap_t cap = cap_get_proc();
  cap_value_t capv;
  cap_flag_value_t capf;

  cap_get_flag(cap, CAP_NET_ADMIN, CAP_PERMITTED, &capf);

  capv = CAP_NET_ADMIN;
  cap_set_flag(cap, CAP_EFFECTIVE, 1, &capv, CAP_SET);
  cap_set_flag(cap, CAP_PERMITTED, 1, &capv, CAP_SET);
  if(cap_set_proc(cap) == -1) {
    printf("Failed to set the CAP_NET_ADMIN capability.\n");
    return 1;
  }
  */

  getifaddrs(&ifs);
  for (iface = ifs; iface; iface = iface->ifa_next) {
    if (iface->ifa_addr && iface->ifa_addr->sa_family == AF_INET) {
      int sock;
      struct sockaddr_in *sap = (struct sockaddr_in*)iface->ifa_addr;
      struct sockaddr_in dest;
      struct pollfd pfd;
      int bcast = 1;

      printf("Found an IP interface %s\n", iface->ifa_name);
      
      sock = socket(AF_INET, SOCK_RAW, IP_P_ICMP);
      setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(int));
      bind(sock, (struct sockaddr*)sap, sizeof(*sap));
      
      /*
      ipp = &iam.icmp.ip;  
      ipp->version = 4;
      ipp->header_len = sizeof(struct ip_header);
      ipp->tos = 0;
      ipp->id = 0;
      ipp->flags = 0;
      ipp->frag_offset = 0;
      ipp->ttl = 1;
      ipp->proto = IP_P_ICMP;
      ipp->source = sap->sin_addr.s_addr;
      ipp->dest = 0xFFFFFFFF;
      ipp->total_len = sizeof(iam);
      ipp->checksum = 0;
      ipp->checksum = ip_checksum((__u16*)ipp, sizeof(struct ip_header));
      */

      dest.sin_family = AF_INET;
      dest.sin_port = 0;
      dest.sin_addr.s_addr = ((struct sockaddr_in*)(iface->ifa_broadaddr))->sin_addr.s_addr;
      

      iam.icmp.type = ICMP_TYPE_ADDRESS_MASK_REQUEST;
      iam.icmp.code = 0;
      iam.icmp.checksum = 0;
      iam.id = 0;
      iam.seqn = 0;
      memset(iam.network_mask, 0, 4);
      iam.icmp.checksum = ip_checksum((__u16*)&iam, sizeof(iam));

      if (sendto(sock, &iam, sizeof(iam), 0, (struct sockaddr*)&dest, sizeof(dest)) == -1) {
        char *s = strerror(errno);

        printf("Unable to send an ICMP datagram via the interface %s: %s\n", iface->ifa_name, s);

        close(sock);
        continue;
      }

      pfd.fd = sock;
      pfd.events = POLLIN;
      if (poll(&pfd, 1, 2000) > 0) {
        recv(sock, &iam, sizeof(iam), 0);

        printf("The subnet mask of the interface %s is %d.%d.%d.%d\n", 
               iface->ifa_name,
               iam.network_mask[0], iam.network_mask[1], iam.network_mask[2], iam.network_mask[3]);
      }

      close(sock);
    }
  }    

  freeifaddrs(ifs);
}
